package com.osdp.gateway.repository;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.osdp.gateway.config.OsdpGatewayProperties;
import com.osdp.gateway.service.GatewayRouteService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;

/**
 * 实现runner，通过nacos下发动态路由配置
 *
 * CommandLineRunner、ApplicationRunner 接口是在容器启动成功后的最后一步回调（类似开机自启动）。
 * CommandLineRunner接口被用作将其加入spring容器中时执行其run方法。多个CommandLineRunner可以被同时执行在同一个spring上下文中并且执行顺序是以order注解的参数顺序一致。
 *
 * ApplicationEventPublisherAware 是由 Spring 提供的用于为 Service 注入 ApplicationEventPublisher 事件发布器的接口，使用这个接口，我们自己的 Service 就拥有了发布事件的能力。
 * 用户注册后，不再是显示调用其他的业务 Service，而是发布一个用户注册事件。
 *
 * RouteDefinitionLocator 是路由定义定位器的顶级接口，它的主要作用就是读取路由的配置信息
 */
@Slf4j
public class NacosDynamicRouteRepository implements CommandLineRunner, RouteDefinitionRepository, ApplicationEventPublisherAware {
//    private final Logger log = LoggerFactory.getLogger(NacosDynamicRouteRepository.class);

    @Autowired
    private OsdpGatewayProperties osdpGatewayProperties;

    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private GatewayRouteService gatewayRouteService;

    //nacos配置存储
    private static final CopyOnWriteArrayList<RouteDefinition> GATEWAY_ROUTES = new CopyOnWriteArrayList<RouteDefinition>();

    /**
     * Java中该注解的说明：@PostConstruct该注解被用来修饰一个非静态的void（）方法。
     * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行，并且只会被服务器执行一次。
     * PostConstruct在构造函数之后执行，init（）方法之前执行。
     */
    @PostConstruct
    private void init(){
        publishRoutes();
    }
    /**
     * 创建nacos配置服务
     * @return ConfigService
     */
    private ConfigService getConfigService()   {
        ConfigService configService = null;
        try {
            configService = NacosFactory.createConfigService(osdpGatewayProperties.getServerAddr());
        }catch (NacosException e){
            e.printStackTrace();
        }
        return configService;
    }

    /**
     * 更新缓存路由配置信息
     * @param content
     */
    private synchronized void refreshRoutes(String content){
        GATEWAY_ROUTES.clear();
        GATEWAY_ROUTES.addAll(getListByConfig(content));
        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * 根据配置信息得到初始化Nacos配置
     *
     */
    private void publishRoutes()  {
        String config = null;
        try {
            config = getConfigService().getConfig(osdpGatewayProperties.getDataId(),
                    osdpGatewayProperties.getGroup(), osdpGatewayProperties.getTimeout());
            if(log.isInfoEnabled()) {
                log.info("初始化网关路由信息:{}", config);
            }
        }catch (NacosException e){
            e.printStackTrace();
        }
        if(config != null){
            refreshRoutes(config);
        }
    }

    /**
     * 解决nacos配置
     * @param config String
     * @return result List
     */
    private List<RouteDefinition> getListByConfig(String config){
        List<RouteDefinition> result = null;
        if(StringUtils.isNotEmpty(config)){
            try {
                result = JSONObject.parseArray(config, RouteDefinition.class);
                if(log.isInfoEnabled()) {
                    log.info("网关路由配置信息:{}", result);
                }
            }catch (Exception e){
                e.printStackTrace();
                if(log.isErrorEnabled()){ log.error(e.getMessage());}
            }
        }
        if(result == null) {return Collections.emptyList();}
        for(RouteDefinition router : result){
            String id = StringUtils.substringAfter(router.getId(), ":");//截取":"后面的字符
            router.setId(id);
        }
        return result;
    }
    /**
     * 监听Nacos Server下发的动态路由配置
     */
    public void dynamicRouteByNacosListener (){
        try {
            this.getConfigService().addListener(osdpGatewayProperties.getDataId(), osdpGatewayProperties.getGroup(), new Listener() {
                @Override
                public Executor getExecutor() {
                    return null;
                }

                @Override
                public void receiveConfigInfo(String configInfo) {
                    if(log.isInfoEnabled()) {
                        log.info("动态监听网关路由信息:{}", configInfo);
                    }
                    refreshRoutes(configInfo);
                }
            });
        }catch(NacosException e){
            e.printStackTrace();
        }
    }
    @Override
    public void run(String... args) throws Exception {
        dynamicRouteByNacosListener();
    }
    /**
     * 路由定义定位器接口，只有getRouteDefinitions()一个方法，用来获取路由定义列表的方法。
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        gatewayRouteService.saveDiscoveryRoutes(GATEWAY_ROUTES);
        return Flux.fromIterable(GATEWAY_ROUTES);
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return null;
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return null;
    }


    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}
